package com.es.www.customAop.processor;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.PostConstruct;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.weaver.tools.PointcutExpression;
import org.aspectj.weaver.tools.PointcutParser;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;

/**
 * @Created by michangtao
 * @Date 2022/2/10 17:25
 * @Description
 */
@Component
public class AopProcessor implements BeanPostProcessor, BeanFactoryAware {

    private ConfigurableListableBeanFactory beanFactory;

    private Map<PointcutExpression, Method> beforeMethodMap = new ConcurrentHashMap<>();


    @PostConstruct
    public void init(){
        //获得所有的切面和切入点表达式
        String[] bdNames = beanFactory.getBeanDefinitionNames();
        for (String bdName : bdNames){
            String beanClass = beanFactory.getBeanDefinition(bdName).getBeanClassName();
            Class<?> clazz = ClassUtils.resolveClassName(beanClass, ClassUtils.getDefaultClassLoader());
            if(clazz.isAnnotationPresent(Aspect.class)){//这个bean是个切面类
                //解析切点表达式，缓存下来
                PointcutParser pointcutParser = PointcutParser.getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution();
                ReflectionUtils.doWithMethods(clazz, method -> {
                    Before beforeAspect = method.getAnnotation(Before.class);
                    if(Objects.nonNull(beforeAspect)){
                        //切点表达式
                        String pointCutExp = beforeAspect.value();
                        //解析切点表达式
                        PointcutExpression pointcutExpression = pointcutParser.parsePointcutExpression(pointCutExp);
                        //缓存下来
                        beforeMethodMap.put(pointcutExpression,method);
                    }
                });
            }
        }
    }



    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        Aspect annotation = AnnotationUtils.findAnnotation(bean.getClass(), Aspect.class);
        //切面类不需要增强
        if(Objects.nonNull(annotation)){
            return bean;
        }

        List<Method> proxyMethods = new ArrayList<>();
        //判断当前bean是否满足切点表达式
        beforeMethodMap.forEach((pointcutExpression, method) -> {
            if(pointcutExpression.couldMatchJoinPointsInType(bean.getClass())){
                proxyMethods.add(method);//将通知方法保存下来
            }
        });

        if(CollectionUtils.isEmpty(proxyMethods)){
            return bean;
        }

        //织入通知生成代理对象
        return Enhancer.create(bean.getClass(), (MethodInterceptor) (proxy, method, args, methodProxy) -> {
            // 依次执行前置通知
            // 在方法调用前反射执行切面类的前置通知方法
            for (Method proxyMethod : proxyMethods) {
                Object aspectBean = beanFactory.getBean(proxyMethod.getDeclaringClass());
                proxyMethod.invoke(aspectBean);
            }
            return methodProxy.invokeSuper(proxy, args);
        });
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
    }
}
